JavaScript 中的求值策略

计算机科学中有个概念,叫做求值策略 Evaluation Strategy。它决定了变量之间,函数的实参与形参之间,值是如何传递的。求值策略主要有以下两种:
按值传递按引用传递

按值传递与按引用传递

  • 按值传递(call by value)是最常用的求值策略,函数的形参是被调用时函数实参的副本,修改函数的形参不会影响实参。
  • 按引用传递(call by reference)是另一种求值策略,函数的形参是被调用时函数实参的隐式引用,不再是函数实参的副本,修改函数的形参会相应地影响到实参,因为函数实参传递给函数形参是引用本身。

按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的 BUG。按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低。两种传值方式都有各自的问题。首先明确一点,在 JavaScript 中,不存在按引用传递。那么,是不是 JavaScript 中,变量之间,函数形参与实参之间都是按值传递呢?这是一个有些争议的问题,可以近似认为,JavaScript 中的值都是按值传递。下面分基本类型的值和引用类型的值两种情况来看。

基本类型的值

对于基本类型的值,情况非常简单清晰,考虑以下代码:

1
2
3
4
5
6
7
8
// 示例1
let name = 'Bill'
const f = function(name) {
name = 'Sunny'
}

f(name)
console.log(name) // Bill

上面代码说明,基本类型按照值传递,函数实参只是函数形参的一个副本。也就是说,函数被调用时,引擎会分配一块新的内存来存放从函数实参复制来的函数形参,所以接下来函数内部对形参所做的所有操作都是基于这块新的内存,函数实参自然不会受到影响。

引用类型的值

对于引用类型的值,也就是对象,情况有些不一样。考虑以下代码:

1
2
3
4
5
6
7
8
9
10
// 示例2
let user = {
name: 'Bill'
}
const f = function(user) {
user.name = 'Sunny'
}

f(user)
console.log(user.name) // Sunny

上面的代码说明,传入的对象 user 被函数 f 改变了,那么是否说明对于引用类型的值,是按照引用传递的吗?再考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 示例3
let user = {
name: 'Bill'
}
const f = function(user) {
user = {
name: 'Sunny'
}
// user = null, 结果一样
}

f(user)
console.log(user.name) // Bill

如果 JavaScript 中的对象确实是按引用传递,传入的对象 user 的指向将被改变,那么上面代码最后一行应该打印 Sunny 。然而,实际打印出的仍然是 Bill ,这说明 JavaScript 中对象的值也不是按照引用传递的。事实上,对于对象而言,JavaScript 中函数实参传递给函数形参的是对象地址的一个副本,此时函数实参和形参都指向内存中同一块区域。所以在示例 2 中,修改 user.name 的值才会影响到原 user 对象。

总结

对于这种求值策略,一种观点认为本质上仍然是按值传递,另一种观点认为是按共享传递。叫什么术语其实不重要,重要是理解其内在机制,即调用函数传参时,函数实参传递给函数形参的,是对象的内存地址的副本,既不是按值传递的对象副本,也不是按引用传递的内存地址本身。

参考链接

Evaluation Strategy

Call by value

Call by reference

Call by sharing

http://dmitrysoshnikov.com/-Evaluation Strategy

http://www.cnblogs.com/bosnma/p/4256108.html